/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.iec.edp.caf.data.orm.jpa;

/**
 * @author wangyandong
 * @date 2019/9/5 13:02
 *
 */

import io.iec.edp.caf.commons.runtime.CafEnvironment;
import io.iec.edp.caf.commons.runtime.env.enums.GrayReleaseState;
import io.iec.edp.caf.commons.utils.ClassLoaderUtils;
import io.iec.edp.caf.data.orm.jpa.spi.GrayReleaseTableStrategy;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.EmptyInterceptor;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * hibernate拦截器：表名替换、国际化字段替换
 * 单例
 */
@SuppressWarnings({"JavaDoc", "unused"})
@Slf4j
public class CAFHibernateInterceptor extends EmptyInterceptor {

    private static List<String> tableNames;

    private static final String SELECT_SQL = "select";

    private static final String INTO_SQL = "into";
    /**
     * 默认的拦截器构造函数
     */
    public CAFHibernateInterceptor() {

    }

    /**
     * sql执行前拦截器方法
     * @param sql
     * @return
     */
    @Override
    public String onPrepareStatement(String sql) {
        try(DynamicTableContext context = DynamicTableContext.getContext()){
            DynamticTableInfo info = context.getDynamicTableInfo();
            if(info ==null){
                return sql;
            }

            //动态表
            if(info.getDynamicTableNames()!=null) {
                Map<String, String> maps = info.getDynamicTableNames();
                for(Map.Entry<String,String> entry:maps.entrySet()){
                    String srcTableName = entry.getKey();
                    String destTableName = entry.getValue();
                    //仅当sql中不存在目标表时，才进行替换
                    if(Pattern.compile("(?i)\\s" + destTableName + "\\s").matcher(sql).find() == false) {
                        //如果存在 table1 table1 模式的表别名
                        if(Pattern.compile("(?i)\\s" + srcTableName + "\\s+" + srcTableName + "\\s").matcher(sql).find() == true) {
                            //正则表达式替换: 仅匹配表名前后带空格的情况k
                            sql = sql.replaceAll("(?i)\\s" + srcTableName + "\\s+" + srcTableName + "\\s",
                                    " " + destTableName + " " + srcTableName + " ");
                        }
//                        else if(Pattern.compile("(?i)from\\s.*\\s+" + srcTableName + "\\s" + "where").matcher(sql).find() == true){
//                            //如果出现from nottable1 table1 where 什么也不做
//                            //doNothing
//                            //from dfad table1 where
//                        }
                        else {
                            //正则表达式替换: 仅匹配表名前后带空格的情况
                            sql = sql.replaceAll("(?i)\\s" + srcTableName + "\\s", " " + destTableName + " ");
                        }
                    }
                }
            }
            return sql;
        }
        catch (Exception e){
            log.error(e.getMessage(),e);
            return sql;
        }finally {
            sql = resolveGrayReleaseTable(sql);
        }

    }

    /**
     * 处理灰度表
     * @param sql
     * @return
     */
    private String resolveGrayReleaseTable(String sql){
        //识别灰度表
        List<String> tableNamesInSql = getTableNamesInSql(sql);
        if (CollectionUtils.isEmpty(tableNamesInSql)) {
            return sql;
        }
        //根据环境灰度状态进行不同操作
        GrayReleaseState state = CafEnvironment.getCurrentGrayReleaseState();
        switch (state){
            case NORMAL:
            case UPDATE_A:
                //不操作
                return sql;
            case PREPARE_A:
            case PREPARE_B:
            case STARTING_A:
            case FINISHED_A:
            case STARTING_B:
            case FINISHED_B:
                //只允许读操作
                if (isWriteSql(sql)) {
                    throw new RuntimeException("服务更新中，请稍后再试。");
                }
        }
        switch (state){
            case UPDATE_B:
            case STARTING_B:
            case FINISHED_B:
                //替换灰度表名称
                sql = replaceTableName(sql, tableNamesInSql);
        }
        return sql;
    }

    /**
     * 判断sql是否是写入
     * @param sql
     * @return
     */
    private boolean isWriteSql(String sql) {
        // sql语句不是以select开头，或者语句是select * into格式
        return !Pattern.compile("(?i)^" + SELECT_SQL + "\\s").matcher(sql).find() || Pattern.compile("(?i)^" + SELECT_SQL + "\\s" + ".+" + "\\s" + INTO_SQL).matcher(sql).find();
    }

    /**
     * 获取灰度列表中的表名
     * @param sql
     * @return
     */
    private List<String> getTableNamesInSql(String sql) {
        if(tableNames == null){
            tableNames = new ArrayList<>();
            // 第一次替换表名时从配置文件中读取此次灰度更新过程中克隆出的灰度表
            ServiceLoader<GrayReleaseTableStrategy> sl = ServiceLoader.load(GrayReleaseTableStrategy.class, ClassLoaderUtils.getServiceClassLoader());
            for(GrayReleaseTableStrategy strategy:sl){
                tableNames.addAll(strategy.getTableNames());
            }
            tableNames.addAll(CafEnvironment.getGrayReleaseTables());
        }
        if (CollectionUtils.isEmpty(tableNames)) {
            return null;
        }
        // 过滤出sql中包含的灰度表名
        final String finalSql = sql;
        return tableNames.stream().filter(tableName -> Pattern.compile("(?i)\\s" + tableName + "\\s").matcher(finalSql).find()).collect(Collectors.toList());
    }

    private String replaceTableName(String sql, List<String> tableNamesInSql) {
        for (String tableName : tableNamesInSql) {
            // sql中可能存在多个tableName，都要替换
            String destTableName = tableName + CafEnvironment.getGrayTableSUFFIX();
            //如果存在 table1 table1 模式的表别名
            if (Pattern.compile("(?i)\\s" + tableName + "\\s+" + tableName + "\\s").matcher(sql).find()) {
                //正则表达式替换: 仅匹配表名前后带空格的情况k
                sql = sql.replaceAll("(?i)\\s" + tableName + "\\s+" + tableName + "\\s", " " + destTableName + " " + tableName + " ");
            } else {
                //正则表达式替换: 仅匹配表名前后带空格的情况
                sql = sql.replaceAll("(?i)\\s" + tableName + "\\s", " " + destTableName + " ");
            }
        }
        return sql;
    }
}
